[TMP] Feature Preview - #510
Draft
nicklafleur wants to merge 9 commits into
Draft
Conversation
This was referenced Apr 27, 2026
Config still exposed Config.get()/ensure_loaded()/reset() classmethods while MutmutState (added by boxed#509) already used a bare config()/state()-style accessor. Replace the classmethod API with module-level config()/reset_config() functions to match, and migrate the remaining module-level globals in mutmut/__init__.py (stats_time, duration_by_test, tests_by_mangled_function_name, _stats, _covered_lines) onto MutmutState, keeping deprecated __getattr__ shims for external readers of the old mutmut.* attributes. Renames local variables that shadowed the new config()/state() function names where needed to avoid UnboundLocalError, and repoints test fixtures/monkeypatches that patched Config.get() at the class/module level to instead mutate the live config()/state() singleton instances in place, so patches stay visible to modules that already imported config by reference.
Move timeout management from threading/ to workers/ package. - Move src/mutmut/threading/timeout.py -> src/mutmut/workers/timeout.py - Update __main__.py import to use new location - Move tests/threading/ -> tests/workers/
Non-functional reorganization: move cohesive groups of code out of the oversized __main__.py into dedicated modules. No behavior change. New/extended modules: - stats.py — status/emoji maps, Stat, collect_stat, calculate_summary_stats, print_stats, load_stats, save_stats - runners/harness.py — TestRunner ABC, PytestRunner, HammettRunner, ListAllTestsResult, collected_test_names, unused, and the test-runner exceptions (Collect/BadTestExecutionCommands) - ui/browse.py — ResultBrowser Textual app wrapped in run_result_browser(); get_diff_for_mutant/apply_mutant are injected to avoid a circular import back into __main__. Uses upstream boxed#543's @work/Lock/get_current_worker diff-loading model. - ui/terminal.py — spinner + status_printer/print_status - utils/file_utils.py — walk_all_files/walk_source_files/ walk_mutatable_files, copy_src_dir, copy_also_copy_files, setup_source_paths (alongside the existing change_cwd) Move result_browser_layout.tcss into ui/ so Textual's CSS_PATH resolves relative to ui/browse.py. __main__.py re-imports the moved public names, so external `from mutmut.__main__ import ...` call sites keep working unchanged. Repoint code_coverage.py's TYPE_CHECKING import to runners.harness. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add pipe-based fork isolation helpers (run_in_fork_with_result, run_in_fork) that run functions in forked children so the parent process never imports pytest/conftest and stays fork-safe. This is the foundation for the upcoming hot-fork runner where the parent acts purely as an orchestrator. Also add OrchestratorCrashError, which reports the crashed orchestrator's exit code, the lost in-flight mutants (truncated past 10), an optional crash-log path, and instructions to resume with 'mutmut run'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Introduce a MutantRunner ABC that owns the process-isolation strategy for testing mutants and also fronts the surrounding test operations (stats collection, clean tests, forced-fail, test listing), so __main__ no longer drives os.fork() directly. ForkRunner encapsulates the traditional os.fork()-per-mutant loop that lived inline in _run(): submit() forks a child under a CPU/wall timeout, and wait_for_result() reaps one child into a MutantResult. get_mutant_runner() selects the runner from the new process_isolation config (ProcessIsolation enum; only 'fork' is wired up here, 'hot-fork' raises pending a later commit). _run() now drives the runner through submit/has_capacity/wait_for_result/ pending_count/shutdown and registers results by mutant name. The stale-stats protections are preserved verbatim: _check_test_to_mutant_associations() still runs, and collect_or_load_stats() keeps its apply_config_invalidation path (now routed through MutantRunner.collect_stats/list_all_tests). Behavior for the default fork path is unchanged; the full suite (incl. the e2e run) is green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a single-orchestrator process-isolation strategy for projects whose test
setup makes the parent fork-unsafe (gevent monkey-patching, grpc, torch):
parent (clean) -> orchestrator (imports pytest once) -> N grandchildren
The parent never imports pytest/conftest. The orchestrator imports pytest a
single time, warms up (configurable via hot_fork_warmup: collect/import/none),
then forks one grandchild per mutant, streaming results back over a pipe and
reaping via a SIGCHLD self-pipe. If the orchestrator crashes, in-flight mutants
are re-submitted to a fresh orchestrator up to max_orchestrator_restarts times
before raising OrchestratorCrashError. Stats/clean-test/forced-fail/test-listing
all run in short-lived forks (StatsResult carries the collected mapping back to
the parent), so the parent stays clean throughout.
Selected via process_isolation = "hot-fork"; get_mutant_runner() now builds it.
Supporting pieces: HotForkWarmup config + validation, TestRunner.warm_up(),
models/results.StatsResult, and utils/logging_utils for the orchestrator's
file-only logging and crash logs.
Validated end-to-end: on the my_lib project, hot-fork produces byte-identical
verdicts to fork (112 mutants, same 37/64/10/1 distribution). Adds a subprocess
e2e smoke test (hot_fork_basic) plus factory/config-validation unit tests.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Groundwork for the result browser's dependency visualization and cache tracking. Pure logic, no Textual imports, so it is unit-tested on its own: - models/cache_status.CacheStatus (CACHED/STALE_DEPENDENCY/INVALID) with severity ordering + CACHE_STATUS_EMOJI. - ui/helpers: dependency BFS over state().function_dependencies (expand_changed_functions, get_ordered_upstream_and_downstream_functions, compute_funcs_with_invalid_deps, find_invalid_dependencies) and per-mutant get_cache_status. - format_utils.mangled_name_from_mutant_name + raw_func_name_from_mangled (mangled<->raw name conversion for dependency lookup). - Config.get_effective_dependency_depth() (tracking depth clamped to max_stack_depth), adapted to this branch's -1 sentinel. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Layer the dependency/cache features onto the boxed#543 browser (keeping its @work/Lock/get_current_worker diff loader untouched): - A two-column upstream(callers)/downstream(callees) dependencies table shown beside the diff (3:2), populated by BFS over state().function_dependencies. - A cache-status column on both the files and mutants tables (CACHED/STALE/ INVALID) computed from per-function hash changes + invalid-dependency propagation; the files table rolls up to its worst mutant. - A depth toggle (v) cycling 1-lvl / configured-depth / full, with per-(func, depth) BFS memoization. - Retest-invalid-dependencies (d) and, to make the browser's 'g' binding real, a new `generate` CLI command that regenerates mutants and refreshes hashes/ stats without running the mutation loop (`--no-invalidate-callers` keeps the caller edges). The generation prefix of `_run` is extracted into a shared `_generate_mutants_and_collect_stats` helper so both commands stay in sync. Kept this branch's string-based status handling (status_by_exit_code/ emoji_by_status) rather than pulling in the reference's MutantStatus enum, so the diff stays confined to the new features. Validated: headless Pilot mount test (files/mutants/deps tables populate, cache column present, depth toggle works) + a subprocess `generate` e2e. Full suite 399 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Consolidate the mangled-name helpers into `utils/format_utils.py` so they live next to the other key/name utilities and can be imported without pulling in `__main__` or `trampoline_templates`: - Move `CLASS_NAME_SEPARATOR` + `mangle_function_name` out of `mutation/trampoline_templates.py` (which no longer referenced them beyond the definition) into `format_utils`; `file_mutation` now imports `mangle_function_name` from there. - Move `mangled_name_from_mutant_name` + `orig_function_and_class_names_from_key` out of `__main__` into `format_utils`, removing the temporary `mangled_name_from_mutant_name` duplicate introduced with the browser helpers. `__main__` re-imports both (so `from mutmut.__main__ import ...` still works), and `mutation/trampoline.py` now imports the helper from `format_utils`, dropping one `__main__` import cycle. `format_utils` is now a leaf module (stdlib only). Pure move, no behavior change; full suite is green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
nicklafleur
force-pushed
the
nicklafleur/oss-complete
branch
from
August 14, 2026 19:25
ca63580 to
06ce4cf
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This PR serves as a public preview of our in-flight work